export function loadAddonMgr() {
    (function (global, factory) {

        "use strict";

        if (typeof module === "object" && typeof module.exports === "object") {
            module.exports = factory(global, true);
        } else {
            if (typeof define === 'function' && define.amd) {
                define(factory);
            } else {
                factory(global);
            }
        }

    })(typeof window !== "undefined" ? window : this, function (window, noGlobal) {

        "use strict";

        /**
         * http状态码，对应的返回信息，由于websocket不存在状态码，暂时由前端对返回信息进行匹配
         错误码：406   错误信息：Failed to send message to WPS
         错误码：406   错误信息：Json parse failed
         错误码：406   错误信息：Product not found
         错误码：406   错误信息：Failed
         错误码：403   错误信息：Forbidden
         错误码：503   错误信息：Service Unavailable
         错误码：417   错误信息：WPSInnerMessage_InvalidBusinessId
         */
        var httpCodeErrorMessageList = [
            'Failed to send message to WPS',
            'Json parse failed',
            'Product not found',
            'Failed',
            'Forbidden',
            'Service Unavailable',
            'WPSInnerMessage_InvalidBusinessId'
        ]

        var bFinished = true;

        var serverVersion = 'wait';
        var wpsinvokeNetType = 'wait';

        if (sessionStorage.getItem('wpsinvokeNetType')) {
            wpsinvokeNetType = sessionStorage.getItem('wpsinvokeNetType');
        }
        ;

        if (sessionStorage.getItem('serverVersion')) {
            serverVersion = sessionStorage.getItem('serverVersion');
        }
        ;

        function isReplaceWebSocket() {
            if (wpsinvokeNetType === 'websocket') {
                return true;
            } else {
                return false;
            }
            ;
        }

        /**
         * ws、http互相替换链接
         * @param {*} url 请求链接
         * @returns
         */
        function transferWebsocketToHttp(url) {
            return url.replace('ws://', 'http://').replace(':58892', ':58890');
        }

        function transferHttpToWebsocket(url) {
            return url.replace('http://', 'ws://').replace(':58890', ':58892');
        }

        /**
         * 获取网页路径前缀
         * @returns url前缀
         */
        function GetUrlBase() {
            if (isReplaceWebSocket()) {
                if (location.protocol == "https:")
                    return 'wss://127.0.0.1:58893'
                return 'ws://127.0.0.1:58892'
            }
            return getHttpUrl();
        }

        /**
         * 获取http链接
         */
        function getHttpUrl() {
            if (location.protocol == "https:")
                return "https://127.0.0.1:58891"
            return "http://127.0.0.1:58890"
        }

        /**
         * 兼容IE低版本Array没有indexOf方法的场景
         */
        if (!Array.prototype.indexOf) {
            Array.prototype.indexOf = function (elt /*, from*/) {
                var len = this.length >>> 0;

                var from = Number(arguments[1]) || 0;
                from = (from < 0)
                    ? Math.ceil(from)
                    : Math.floor(from);
                if (from < 0)
                    from += len;

                for (; from < len; from++) {
                    if (from in this &&
                        this[from] === elt)
                        return from;
                }
                return -1;
            };
        }

        /**
         * 判断是否函数
         */
        function isFunction(func) {
            return typeof func === 'function';
        }

        /**
         * websocket对象
         */
        function getWebsocketObj() {
            this.onopen = null;
            this.onmessage = null;
            this.onclose = null;
            this.onerror = null;
            this.wsInstance = null;
        }

        getWebsocketObj.prototype.connect = function (url) {
            var that = this;
            var ws = that.wsInstance = new WebSocket(url);
            ws.onopen = function (res) {
                if (isFunction(that.onopen)) {
                    that.onopen(res);
                }
            }

            ws.onmessage = function (res) {
                if (isFunction(that.onmessage)) {
                    that.onmessage(res);
                }
            }

            ws.onclose = function (res) {
                if (isFunction(that.onclose)) {
                    that.onclose(res);
                }
            }

            ws.onerror = function (res) {
                if (isFunction(that.onerror)) {
                    that.onerror(res);
                }
            }
        }

        getWebsocketObj.prototype.send = function (data) {
            if (this.wsInstance) {
                this.wsInstance.send(data)
            }
        }

        getWebsocketObj.prototype.close = function () {
            if (this.wsInstance) {
                this.wsInstance.close()
            }
        }


        /**
         * 兼容IE低版本的创建httpobj对象的方法
         * @returns httpobj，可用于进行数据传输的http的对象
         */
        function getHttpObj() {
            var httpobj = null;
            if (IEVersion() < 10) {
                try {
                    httpobj = new XDomainRequest();
                } catch (e1) {
                    httpobj = new createXHR();
                }
            } else {
                httpobj = new createXHR();
            }
            return httpobj;
        }

        //兼容IE低版本的创建xmlhttprequest对象的方法
        /**
         * 兼容IE低版本的创建xmlhttprequest对象的方法
         * @returns xmlhttprequest对象，兼容低版本IE
         */
        function createXHR() {
            if (typeof XMLHttpRequest != 'undefined') { //兼容高版本浏览器
                return new XMLHttpRequest();
            } else if (typeof ActiveXObject != 'undefined') { //IE6 采用 ActiveXObject， 兼容IE6
                var versions = [ //由于MSXML库有3个版本，因此都要考虑
                    'MSXML2.XMLHttp.6.0',
                    'MSXML2.XMLHttp.3.0',
                    'MSXML2.XMLHttp'
                ];

                for (var i = 0; i < versions.length; i++) {
                    try {
                        return new ActiveXObject(versions[i]);
                    } catch (e) {
                        //跳过
                    }
                }
            } else {
                throw new Error('您的浏览器不支持XHR对象');
            }
        }

        /**
         * 加载项本地服务返回的错误信息，统一为data ： message的json格式
         * 如果有新增错误信息，添加到下面
         * 也可以通过请求状态码返回错误信息
         */
        var errorMsg = [
            "{\"data\": \"Failed to send message to WPS.\"}",
            "{\"data\": \"Json parse failed.\"}",
            "{\"error\": \"Json parse failed.\"}"
        ]

        /**
         * 通过该接口给服务器发请求
         * @param {*} options       参数对象，具体包含的参数如下：
         * @param {*} url           网页路径，传输地址
         * @param {*} type          传输类型，POST / GET / PUT
         * @param {*} sendData      传输的数据
         * @param {*} callBack      回调函数
         * @param {*} tryCount      请求失败后的尝试次数
         * @param {*} bPop          是否弹出浏览器提示对话框
         * @param {*} timeout       请求等待响应的时间，超过时间请求实效
         * @param {*} concurrent    请求是否同步发送
         * @param {*} wpsclient        wpsclient对象
         * @returns NULL
         */
        function startWps(options) {
            if (!bFinished && !options.concurrent) {
                if (options.callBack)
                    options.callBack({
                        status: 1,
                        message: "上一次请求没有完成"
                    });
                return;
            }
            if (!options.concurrent)
                bFinished = false;
            else
                options.bFinished = false;

            function startWpsInnder(tryCount) {
                if (tryCount <= 0) {
                    // http失败重试时，会唤醒云服务，当重试次数清0时，向云服务发起一次ws请求确认是否是浏览器安全问题
                    if (serverVersion === 'wait' && wpsinvokeNetType === 'wait') {
                        options.url = transferHttpToWebsocket(options.url);
                        startWpsInnderWebsocket(1);
                        return
                    }
                    ;
                    if (!options.concurrent) {
                        if (bFinished)
                            return;
                        bFinished = true;
                    } else {
                        if (options.bFinished) {
                            return;
                        }
                        options.bFinished = true;
                    }
                    if (options.callBack)
                        options.callBack({
                            status: 2,
                            message: "请允许浏览器打开WPS Office"
                        });
                    return;
                }
                var xmlReq = getHttpObj();
                //WPS客户端提供的接收参数的本地服务，HTTP服务端口为58890，HTTPS服务端口为58891
                //这俩配置，取一即可，不可同时启用
                xmlReq.open('POST', options.url);
                xmlReq.onload = function (res) {
                    wpsinvokeNetType = "http"
                    sessionStorage.setItem("wpsinvokeNetType", wpsinvokeNetType);
                    var responseStr = IEVersion() < 10 ? xmlReq.responseText : res.target.response;
                    var respStatus = IEVersion() < 10 ? xmlReq.status : res.target.status;
                    if (!options.concurrent)
                        bFinished = true;
                    else
                        options.bFinished = true;
                    if (options.callBack) {
                        if ((respStatus != undefined && respStatus != 200) || errorMsg.indexOf(responseStr) != -1) {
                            var errorMessage = JSON.parse(responseStr)
                            options.callBack({
                                status: 1,
                                message: errorMessage.data
                            });
                            if (errorMessage.data == "Subserver not available." && tryCount == options.tryCount && options.bPop) {
                                InitWpsCloudSvr();
                                setTimeout(function () {
                                    startWpsInnder(tryCount - 1)
                                }, 3000);
                            }
                        } else {
                            options.callBack({
                                status: 0,
                                response: responseStr
                            });
                        }
                    }
                }
                xmlReq.ontimeout = xmlReq.onerror = function (res) {
                    xmlReq.bTimeout = true;
                    if (tryCount == options.tryCount && options.bPop && wpsinvokeNetType == "wait") { //打开wps并传参
                        InitWpsCloudSvr();
                    }
                    setTimeout(function () {
                        startWpsInnder(tryCount - 1)
                    }, 1000);
                }
                if (IEVersion() < 10) {
                    xmlReq.onreadystatechange = function () {
                        if (xmlReq.readyState != 4)
                            return;
                        if (xmlReq.bTimeout) {
                            return;
                        }
                        if (xmlReq.status === 200)
                            xmlReq.onload();
                        else
                            xmlReq.onerror();
                    }
                }
                xmlReq.timeout = options.timeout;
                xmlReq.send(options.sendData)
            }

            function startWpsInnderWebsocket(tryCount) {
                // 重试次数结束时，标记请求已完成
                if (tryCount <= 0) {
                    if (!options.concurrent) {
                        if (bFinished)
                            return;
                        bFinished = true;
                    } else {
                        if (options.bFinished) {
                            return;
                        }
                        options.bFinished = true;
                    }
                    if (options.callBack)
                        options.callBack({
                            status: 2,
                            message: "请允许浏览器打开WPS Office"
                        });
                    return;
                }
                var ws = new getWebsocketObj({timeout: options.timeout});
                ws.connect(options.url);
                ws.onmessage = function (res) {
                    wpsinvokeNetType = "websocket"
                    sessionStorage.setItem("wpsinvokeNetType", wpsinvokeNetType);
                    var responseStr = res.data;
                    if (!options.concurrent)
                        bFinished = true;
                    else
                        options.bFinished = true;
                    if (options.callBack) {
                        if (errorMsg.indexOf(responseStr) != -1 || httpCodeErrorMessageList.indexOf(responseStr) != -1) {
                            var errorMessage = JSON.parse(responseStr);
                            options.callBack({
                                status: 1,
                                message: errorMessage.data
                            });
                            if (errorMessage.data == "Subserver not available." && tryCount == options.tryCount && options.bPop) {
                                InitWpsCloudSvr();
                                setTimeout(function () {
                                    startWpsInnderWebsocket(tryCount - 1)
                                }, 3000);
                            }
                        } else {
                            options.callBack({
                                status: 0,
                                response: res.data
                            });
                        }
                    }
                    ws.close();
                }
                ws.onerror = function (res) {

                    if (tryCount == options.tryCount && options.bPop && wpsinvokeNetType == "wait") { //打开wps并传参
                        InitWpsCloudSvr();
                    }
                    setTimeout(function () {
                        startWpsInnderWebsocket(tryCount - 1)
                    }, 1000);
                }
                ws.onopen = function (res) {
                    ws.send(options.sendData);
                }
            }

            if (isReplaceWebSocket()) {
                startWpsInnderWebsocket(options.tryCount);
            } else {
                startWpsInnder(options.tryCount);
            }
            return;
        }

        var fromCharCode = String.fromCharCode;
        // encoder stuff
        var cb_utob = function (c) {
            if (c.length < 2) {
                var cc = c.charCodeAt(0);
                return cc < 0x80 ? c :
                    cc < 0x800 ? (fromCharCode(0xc0 | (cc >>> 6)) +
                            fromCharCode(0x80 | (cc & 0x3f))) :
                        (fromCharCode(0xe0 | ((cc >>> 12) & 0x0f)) +
                            fromCharCode(0x80 | ((cc >>> 6) & 0x3f)) +
                            fromCharCode(0x80 | (cc & 0x3f)));
            } else {
                var cc = 0x10000 +
                    (c.charCodeAt(0) - 0xD800) * 0x400 +
                    (c.charCodeAt(1) - 0xDC00);
                return (fromCharCode(0xf0 | ((cc >>> 18) & 0x07)) +
                    fromCharCode(0x80 | ((cc >>> 12) & 0x3f)) +
                    fromCharCode(0x80 | ((cc >>> 6) & 0x3f)) +
                    fromCharCode(0x80 | (cc & 0x3f)));
            }
        };
        var re_utob = /[\uD800-\uDBFF][\uDC00-\uDFFFF]|[^\x00-\x7F]/g;
        var utob = function (u) {
            return u.replace(re_utob, cb_utob);
        };
        var _encode = function (u) {
            var isUint8Array = Object.prototype.toString.call(u) === '[object Uint8Array]';
            if (isUint8Array)
                return u.toString('base64')
            else
                return btoa(utob(String(u)));
        }

        if (typeof window.btoa !== 'function') window.btoa = func_btoa;

        function func_btoa(input) {
            var str = String(input);
            var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
            for (
                // initialize result and counter
                var block, charCode, idx = 0, map = chars, output = '';
                // if the next str index does not exist:
                //   change the mapping table to "="
                //   check if d has no fractional digits
                str.charAt(idx | 0) || (map = '=', idx % 1);
                // "8 - idx % 1 * 8" generates the sequence 2, 4, 6, 8
                output += map.charAt(63 & block >> 8 - idx % 1 * 8)
            ) {
                charCode = str.charCodeAt(idx += 3 / 4);
                if (charCode > 0xFF) {
                    throw new InvalidCharacterError("'btoa' failed: The string to be encoded contains characters outside of the Latin1 range.");
                }
                block = block << 8 | charCode;
            }
            return output;
        }

        if (typeof window.atob !== 'function') window.atob = func_atob;

        function func_atob(input) {
            var output = input.replace(/[\s\S]{1,4}/g, cb_decode);
            return output
        }

        function cb_decode(cccc) {
            var len = cccc.length
                , padlen = len % 4
                , n = (len > 0 ?
                        b64tab[cccc.charAt(0)] << 18 : 0) |
                    (len > 1 ? b64tab[cccc.charAt(1)] << 12 : 0)
                    | (len > 2 ? b64tab[cccc.charAt(2)] << 6 : 0)
                    | (len > 3 ? b64tab[cccc.charAt(3)] : 0)
                , chars = [
                    fromCharCode(n >>> 16),
                    fromCharCode((n >>> 8) & 0xff),
                    fromCharCode(n & 0xff)
                ]
            ;
            chars.length -= [0, 0, 2, 1][padlen];
            return chars.join('');
        };

        function btou(b) {
            return b.replace(re_btou, cb_btou);
        };

        // decoder stuff
        var re_btou = new RegExp([
            '[\xC0-\xDF][\x80-\xBF]',
            '[\xE0-\xEF][\x80-\xBF]{2}',
            '[\xF0-\xF7][\x80-\xBF]{3}'
        ].join('|'), 'g');

        function cb_btou(cccc) {
            switch (cccc.length) {
                case 4:
                    var cp = ((0x07 & cccc.charCodeAt(0)) << 18)
                            | ((0x3f & cccc.charCodeAt(1)) << 12)
                            | ((0x3f & cccc.charCodeAt(2)) << 6)
                            | (0x3f & cccc.charCodeAt(3)),
                        offset = cp - 0x10000;
                    return (fromCharCode((offset >>> 10) + 0xD800)
                        + fromCharCode((offset & 0x3FF) + 0xDC00));
                case 3:
                    return fromCharCode(
                        ((0x0f & cccc.charCodeAt(0)) << 12)
                        | ((0x3f & cccc.charCodeAt(1)) << 6)
                        | (0x3f & cccc.charCodeAt(2))
                    );
                default:
                    return fromCharCode(
                        ((0x1f & cccc.charCodeAt(0)) << 6)
                        | (0x3f & cccc.charCodeAt(1))
                    );
            }
        };

        /**
         * 将字符串进行Base64编码
         * @param {*} u         需要编码的数据
         * @param {*} urisafe   返回值，编码后的数据
         * @returns             urisafe
         */
        var encode = function (u, urisafe) {
            return !urisafe ?
                _encode(u) :
                _encode(String(u)).replace(/[+\/]/g, function (m0) {
                    return m0 == '+' ? '-' : '_';
                }).replace(/=/g, '');
        };

        var decode = function (u) {
            return btou(atob(String(u)));
        }

        /**
         * 获取IE浏览器版本
         * @returns     IE浏览器版本
         */
        function IEVersion() {
            var userAgent = navigator.userAgent; //取得浏览器的userAgent字符串
            var isIE = userAgent.indexOf("compatible") > -1 && userAgent.indexOf("MSIE") > -1; //判断是否IE<11浏览器
            var isEdge = userAgent.indexOf("Edge") > -1 && !isIE; //判断是否IE的Edge浏览器
            var isIE11 = userAgent.indexOf('Trident') > -1 && userAgent.indexOf("rv:11.0") > -1;
            if (isIE) {
                var reIE = new RegExp("MSIE (\\d+\\.\\d+);");
                reIE.test(userAgent);
                var fIEVersion = parseFloat(RegExp["$1"]);
                if (fIEVersion == 7) {
                    return 7;
                } else if (fIEVersion == 8) {
                    return 8;
                } else if (fIEVersion == 9) {
                    return 9;
                } else if (fIEVersion == 10) {
                    return 10;
                } else {
                    return 6; //IE版本<=7
                }
            } else if (isEdge) {
                return 20; //edge
            } else if (isIE11) {
                return 11; //IE11
            } else {
                return 30; //不是ie浏览器
            }
        }

        /**
         * 启动wps客户端，加载项执行操作，无返回值
         * @param {*} options       参数对象，详情见下：
         * @param {*} clientType    加载项类型， wps / wpp / et
         * @param {*} name          加载项名称
         * @param {*} func          客户端加载项要执行的方法
         * @param {*} param         客户端家乡执行方法的参数
         * @param {*} urlBase       网页路径前缀
         * @param {*} callBack      回调函数
         * @param {*} tryCount      请求失败后的尝试次数
         * @param {*} bPop          是否弹出浏览器提示对话框
         * @param {*} wpsclient     wpsclient对象
         */
        function WpsStart(options) {
            var startInfo = {
                "name": options.name,
                "function": options.func,
                "info": options.param.param,
                "jsPluginsXml": options.param.jsPluginsXml
            };
            var strData = JSON.stringify(startInfo);
            if (IEVersion() < 10) {
                try {
                    eval("strData = '" + JSON.stringify(startInfo) + "';");
                } catch (err) {

                }
            }

            var baseData = encode(strData);
            var url = options.urlBase + "/" + options.clientType + "/runParams";
            var data = "ksowebstartup" + options.clientType + "://" + baseData;
            startWps({
                url: url,
                sendData: data,
                callBack: options.callBack,
                tryCount: options.tryCount,
                bPop: options.bPop,
                timeout: 5000,
                concurrent: false,
                wpsclient: options.wpsclient
            });
        }

        /**
         * 服务端版本为空时，通过该接口启动wps
         * @param {*} options       参数对象，详情见下：
         * @param {*} clientType    加载项类型， wps / wpp / et
         * @param {*} name          加载项名称
         * @param {*} func          客户端加载项要执行的方法
         * @param {*} param         客户端家乡执行方法的参数
         * @param {*} urlBase       网页路径前缀
         * @param {*} callBack      回调函数
         * @param {*} wpsclient     wpsclient对象
         * @param {*} concurrent    请求是否同步发送
         */
        function WpsStartWrap(options) {
            WpsStart({
                clientType: options.clientType,
                name: options.name,
                func: options.func,
                param: options.param,
                urlBase: options.urlBase,
                callBack: options.callBack,
                tryCount: 4,
                bPop: true,
                wpsclient: options.wpsclient,
            })
        }

        /**
         * 支持浏览器触发，WPS有返回值的启动
         *
         * @param {*} clientType    组件类型
         * @param {*} name            WPS加载项名称
         * @param {*} func            WPS加载项入口方法
         * @param {*} param            参数：包括WPS加载项内部定义的方法，参数等
         * @param {*} callBack        回调函数
         * @param {*} tryCount        重试次数
         * @param {*} bPop            是否弹出浏览器提示对话框
         */
        function WpsStartWrapExInner(options) {
            var infocontent = options.param.param;
            var cmdId = guid();
            if (!options.wpsclient || options.wpsclient.single) {
                infocontent = JSON.stringify(options.param.param);
                var rspUrl = getHttpUrl() + "/transferEcho/runParams";
                var funcEx = "var res = " + options.func;
                var cbCode = "var xhr = new XMLHttpRequest();xhr.open('POST', '" + rspUrl + "');xhr.send(JSON.stringify({id: '" + cmdId + "', response: res}));" //res 为func执行返回值
                var infoEx = infocontent + ");" + cbCode + "void(0";
                options.func = funcEx;
                infocontent = infoEx;
            }
            var startInfo = {
                "name": options.name,
                "function": options.func,
                "info": infocontent,
                "showToFront": options.param.showToFront,
                "jsPluginsXml": options.param.jsPluginsXml,
            };

            var strData = JSON.stringify(startInfo);
            if (IEVersion() < 10) {
                try {
                    eval("strData = '" + JSON.stringify(startInfo) + "';");
                } catch (err) {

                }
            }

            var baseData = encode(strData);
            var wrapper;

            if (!options.wpsclient || options.wpsclient.single) {
                var url = options.urlBase + "/transfer/runParams";
                var data = "ksowebstartup" + options.clientType + "://" + baseData;
                wrapper = {
                    id: cmdId,
                    app: options.clientType,
                    data: data,
                    serverId: serverId,
                    mode: options.silentMode ? true : false,
                    timeout: options.timeout,
                    startparam: options.startparam,
                };
            } else {
                var url = options.urlBase + "/transferEx/runParams";
                wrapper = {
                    id: options.wpsclient.clientId,
                    app: options.clientType,
                    data: baseData,
                    mode: options.wpsclient.silentMode ? "true" : "false",
                    serverId: serverId,
                    timeout: options.timeout,
                    startparam: options.startparam,
                };
            }
            wrapper = JSON.stringify(wrapper);
            startWps({
                url: url,
                sendData: wrapper,
                callBack: options.callBack,
                tryCount: options.tryCount,
                bPop: options.bPop,
                timeout: 0,
                concurrent: options.concurrent,
                wpsclient: options.wpsclient
            });
        }

        /**
         * 获取服务端版本号的接口
         * @param {*} options       参数对象，详情见下：
         * @param {*} clientType    加载项类型， wps / wpp / et
         * @param {*} name          加载项名称
         * @param {*} func          客户端加载项要执行的方法
         * @param {*} param         客户端家乡执行方法的参数
         * @param {*} urlBase       网页路径前缀
         * @param {*} callBack      回调函数
         * @param {*} wpsclient     wpsclient对象
         * @param {*} concurrent    请求是否同步发送
         */
        function WpsStartWrapVersionInner(options) {
            if (serverVersion == "wait") {
                if (g_isSdkInited == true) {
                    InitWpsCloudSvr();
                }
                startWps({
                    url: options.urlBase + '/version',
                    sendData: JSON.stringify({serverId: serverId}),
                    callBack: function (res) {
                        if (res.status !== 0) {
                            options.callBack(res)
                            return;
                        }
                        serverVersion = res.response;
                        sessionStorage.setItem('serverVersion', serverVersion);
                        // 确定版本不能使用ws后，需要把未启动时定义好的协议调整回来
                        if (wpsinvokeNetType === 'websocket') {
                            options.urlBase = transferWebsocketToHttp(options.urlBase)
                        }
                        if (options.isPublish != true) {
                            options.tryCount = 1
                            options.bPop = false
                            if (serverVersion === "") {
                                WpsStart(options)
                            } else if (serverVersion < "1.0.1" && options.wpsclient) {
                                options.wpsclient.single = true;
                                WpsStartWrapExInner(options);
                            } else {
                                WpsStartWrapExInner(options);
                            }
                        } else {
                            if (serverVersion === "") {
                                callBack({
                                    status: 4,
                                    message: "当前客户端不支持，请升级客户端"
                                })
                            } else {
                                WpsAddonGetAllConfigInner(options.callBack)
                            }
                        }
                    },
                    tryCount: 4,
                    bPop: !g_isSdkInited,
                    timeout: 5000,
                    concurrent: options.concurrent
                });
            } else {
                if (options.isPublish != true) {
                    options.tryCount = 4
                    options.bPop = true
                    if (serverVersion === "") {
                        WpsStartWrap(options)
                    } else if (serverVersion < "1.0.1" && options.wpsclient) {
                        options.wpsclient.single = true;
                        WpsStartWrapExInner(options);
                    } else {
                        WpsStartWrapExInner(options);
                    }
                } else {
                    if (serverVersion === "") {
                        callBack({
                            status: 4,
                            message: "当前客户端不支持，请升级客户端"
                        })
                    } else {
                        WpsAddonGetAllConfigInner(options.callBack)
                    }
                }
            }
        }

        var HeartBeatCode =
            "function getHttpObj() {\n"
            + "            var httpobj = null;\n"
            + "            if (IEVersion() < 10) {\n"
            + "                try {\n"
            + "                    httpobj = new XDomainRequest();\n"
            + "                } catch (e1) {\n"
            + "                    httpobj = new createXHR();\n"
            + "                }\n"
            + "            } else {\n"
            + "                httpobj = new createXHR();\n"
            + "            }\n"
            + "            return httpobj;\n"
            + "        }\n"
            + "        \n"
            + "        function createXHR() {\n"
            + "            if (typeof XMLHttpRequest != 'undefined') {\n"
            + "                return new XMLHttpRequest();\n"
            + "            } else if (typeof ActiveXObject != 'undefined') {\n"
            + "                var versions = [\n"
            + "                    'MSXML2.XMLHttp.6.0',\n"
            + "                    'MSXML2.XMLHttp.3.0',\n"
            + "                    'MSXML2.XMLHttp'\n"
            + "                ];\n"
            + "        \n"
            + "                for (var i = 0; i < versions.length; i++) {\n"
            + "                    try {\n"
            + "                        return new ActiveXObject(versions[i]);\n"
            + "                    } catch (e) {\n"
            + "                        \n"
            + "                    }\n"
            + "                }\n"
            + "            } else {\n"
            + "                throw new Error('您的浏览器不支持XHR对象');\n"
            + "            }\n"
            + "        }\n"
            + "        \n"
            + "        function IEVersion() {\n"
            + "            var userAgent = navigator.userAgent; \n"
            + "            var isIE = userAgent.indexOf('compatible') > -1 && userAgent.indexOf('MSIE') > -1;\n"
            + "            var isEdge = userAgent.indexOf('Edge') > -1 && !isIE; \n"
            + "            var isIE11 = userAgent.indexOf('Trident') > -1 && userAgent.indexOf('rv:11.0') > -1;\n"
            + "            if (isIE) {\n"
            + "                var reIE = new RegExp('MSIE (\\d+\\.\\d+);');\n"
            + "                reIE.test(userAgent);\n"
            + "                var fIEVersion = parseFloat(RegExp['$1']);\n"
            + "                if (fIEVersion == 7) {\n"
            + "                    return 7;\n"
            + "                } else if (fIEVersion == 8) {\n"
            + "                    return 8;\n"
            + "                } else if (fIEVersion == 9) {\n"
            + "                    return 9;\n"
            + "                } else if (fIEVersion == 10) {\n"
            + "                    return 10;\n"
            + "                } else {\n"
            + "                    return 6; \n"
            + "                }\n"
            + "            } else if (isEdge) {\n"
            + "                return 20; \n"
            + "            } else if (isIE11) {\n"
            + "                return 11; \n"
            + "            } else {\n"
            + "                return 30; \n"
            + "            }\n"
            + "        }\n"
            + "        var heartBeatStart = false;\n"
            + "        function checkLastRegTime() {\n"
            + "            var now = new Date().valueOf();\n"
            + "            var TimeGap = now - LastRegTime;\n"
            + "            if (TimeGap > 5000 && !heartBeatStart) {\n"
            + "                HeartBeat();\n"
            + "                heartBeatStart = true;\n"
            + "            }\n"
            + "        }\n"
            + "        \n"
            + "        function HeartBeat() {\n"
            + "            var heartBeatItem = function () {\n"
            + "                var xhr = getHttpObj();\n"
            + "                xhr.onload = function (e) {\n"
            + "                    self.setTimeout(heartBeatItem, 5000);\n"
            + "                }\n"
            + "                xhr.onerror = function (e) {\n"
            + "                    self.setTimeout(heartBeatItem, 5000);\n"
            + "                }\n"
            + "                xhr.ontimeout = function (e) {\n"
            + "                    self.setTimeout(heartBeatItem, 5000);\n"
            + "                }\n"
            + "                xhr.open('POST', 'http://127.0.0.1:58890/askwebnotify', true);\n"
            + "                xhr.timeout = 2000;\n"
            + "                xhr.send(JSON.stringify(paramStr));\n"
            + "            }\n"
            + "            heartBeatItem();\n"
            + "        }\n"
            + "        \n"
            + "        var paramStr;\n"
            + "        var startCheck = false;\n"
            + "        self.addEventListener('message', function (event) {\n"
            + "            var data = event.data;\n"
            + "                paramStr = data.param\n"
            + "                paramStr.heartBeat = true\n"
            + "                LastRegTime = data.LastRegTime;\n"
            + "                if (!startCheck) {\n"
            + "                    startCheck = true;\n"
            + "                    self.setInterval(checkLastRegTime, 5000)\n"
            + "                }\n"
            + "        }, false);\n"

        var HeartBeatCodeWebSocket =
            "  // 判断是否函数 \n"
            + "  function isFunction(func) { \n"
            + "    return typeof func === 'function'; \n"
            + "  } \n"
            + "  // websocket对象 \n"
            + "  function getWebsocketObj() { \n"
            + "    this.onopen = null; \n"
            + "    this.onmessage = null; \n"
            + "    this.onclose = null; \n"
            + "    this.onerror = null; \n"
            + "    this.wsInstance = null; \n"
            + "  } \n"
            + "  getWebsocketObj.prototype.connect = function (url) { \n"
            + "    var that = this; \n"
            + "    var ws = (that.wsInstance = new WebSocket(url)); \n"
            + "    ws.onopen = function (res) { \n"
            + "      if (isFunction(that.onopen)) { \n"
            + "        that.onopen(res); \n"
            + "      } \n"
            + "    }; \n"
            + "    ws.onmessage = function (res) { \n"
            + "      if (isFunction(that.onmessage)) { \n"
            + "        that.onmessage(res); \n"
            + "      } \n"
            + "    }; \n"
            + "    ws.onclose = function (res) { \n"
            + "      if (isFunction(that.onclose)) { \n"
            + "        that.onclose(res); \n"
            + "      } \n"
            + "    }; \n"
            + "    ws.onerror = function (res) { \n"
            + "      if (isFunction(that.onerror)) { \n"
            + "        that.onerror(res); \n"
            + "      } \n"
            + "    }; \n"
            + "  }; \n"
            + "  getWebsocketObj.prototype.send = function (data) { \n"
            + "    if (this.wsInstance) { \n"
            + "      this.wsInstance.send(data); \n"
            + "    } \n"
            + "  }; \n"
            + "  getWebsocketObj.prototype.close = function () { \n"
            + "    if (this.wsInstance) { \n"
            + "      this.wsInstance.close(); \n"
            + "    } \n"
            + "  }; \n"
            + "  var heartBeatStart = false; \n"
            + "  function checkLastRegTime() { \n"
            + "    var now = new Date().valueOf(); \n"
            + "    var TimeGap = now - LastRegTime; \n"
            + "    if (TimeGap > 5000 && !heartBeatStart) { \n"
            + "      HeartBeat(); \n"
            + "      heartBeatStart = true; \n"
            + "    } \n"
            + "  } \n"
            + "  var ws = null \n"
            + "  function HeartBeat() { \n"
            + "    var heartBeatItem = function () { \n"
            + "      if(ws !== null) { \n"
            + "        ws.send(JSON.stringify(paramStr)); \n"
            + "        return; \n"
            + "      } \n"
            + "      ws = new getWebsocketObj(); \n"
            + "      ws.onmessage = function () { \n"
            + "        self.setTimeout(function() { \n"
            + "          ws.send(JSON.stringify(paramStr)) \n"
            + "        }, 5000); \n"
            + "      }; \n"
            + "      ws.onclose = function () { \n"
            + "        ws = null; \n"
            + "        self.setTimeout(heartBeatItem, 5000); \n"
            + "      }; \n"
            + "      ws.connect('ws://127.0.0.1:58892/askwebnotify'); \n"
            + "      ws.onopen = function () { \n"
            + "        ws.send(JSON.stringify(paramStr)); \n"
            + "      }; \n"
            + "    }; \n"
            + "    heartBeatItem(); \n"
            + "  }; \n"
            + "  var paramStr; \n"
            + "  var startCheck = false; \n"
            + "  self.addEventListener( \n"
            + "    'message', \n"
            + "    function (event) { \n"
            + "      var data = event.data; \n"
            + "      paramStr = data.param; \n"
            + "      paramStr.heartBeat = true; \n"
            + "      LastRegTime = data.LastRegTime; \n"
            + "      if (!startCheck) { \n"
            + "        startCheck = true; \n"
            + "        self.setInterval(checkLastRegTime, 5000); \n"
            + "      } \n"
            + "    }, \n"
            + "    false \n"
            + "  ); \n"

        /**
         * 生成guid的接口
         * @returns guid
         */
        function guid() {
            return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
                var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
                return v.toString(16);
            });
        }

        /**
         * 开启多用户的接口
         */
        var serverId = undefined

        function EnableMultiUser() {
            serverId = getServerId();
            InitSdk(true)
        }

        /**
         * 自定义协议启动服务端
         * 默认不带参数serverId，linux未升级之前不要使用多用户
         */
        function InitWpsCloudSvr() {
            if (serverId == undefined)
                window.location.href = "ksoWPSCloudSvr://start=RelayHttpServer"//是否启动wps弹框
            else
                window.location.href = "ksoWPSCloudSvr://start=RelayHttpServer" + "&serverId=" + serverId //是否启动wps弹框
        }

        /**
         * 获取serverId的接口
         * @returns serverId
         */
        function getServerId() {
            if (window.localStorage) {
                if (localStorage.getItem("serverId")) {
                    //
                } else {
                    localStorage.setItem("serverId", guid());
                }
                return localStorage.getItem("serverId");
            } else {
                return guid();
            }
        }

        /**
         * 将字符串转成二进制，这里用来将字符串化后的js代码转成二进制文件
         * @param {*} code
         * @returns js文件对象的url
         */
        function codeToBlob(code) {
            var blob = new Blob([code], {type: 'text/javascript'}); // 生成js文件对象
            var objectURL = window.URL.createObjectURL(blob); // 生成js文件的url
            return objectURL;
        }

        var RegWebNotifyMap = {wps: {}, wpp: {}, et: {}, pdf: {}}
        var bWebNotifyUseTimeout = true

        function WebNotifyUseTimeout(value) {
            bWebNotifyUseTimeout = value ? true : false
        }

        var g_businessId = Number(Math.random().toString().substr(3, 5) + Date.parse(new Date())).toString(36);
        var HeartBeatWorker
        var checkVersionInterval
        if (window.Worker) {
            try {
                var code = '';
                HeartBeatWorkerCallBack()
                checkVersionInterval = setInterval(HeartBeatWorkerCallBack, 1000)

            } catch (error) {
                //
            }
        }

        function HeartBeatWorkerCallBack() {
            if (serverVersion === 'wait') {
                return;
            }
            if (isReplaceWebSocket()) {
                code = HeartBeatCodeWebSocket;
            } else {
                code = HeartBeatCode
            }
            ;
            HeartBeatWorker = new Worker(codeToBlob(code));
            if (checkVersionInterval) {
                clearInterval(checkVersionInterval);
            }
            return;
        }

        var g_LastRegTime;

        /**
         * 注册一个前端页面接收WPS传来消息的方法
         * @param {*} clientType wps | et | wpp
         * @param {*} name WPS加载项的名称
         * @param {*} callBack 回调函数
         * @param {*} wpsclient wpsclient对象
         */
        function RegWebNotify(clientType, name, callBack, wpsclient) {
            if (clientType != "wps" && clientType != "wpp" && clientType != "et" && clientType != "pdf")
                return;
            var paramStr = {}
            if (wpsclient) {
                if (wpsclient.notifyRegsitered == true) {
                    return
                }
                wpsclient.notifyRegsitered = true;
                paramStr = {
                    clientId: wpsclient.clientId,
                    name: name,
                    type: clientType,
                    serverId: serverId
                }

            } else {
                if (typeof callBack != 'function')
                    return
                if (RegWebNotifyMap[clientType][name]) {
                    RegWebNotifyMap[clientType][name] = callBack;
                    return
                }
                var RegWebNotifyID = new Date().valueOf() + ''
                paramStr = {
                    id: RegWebNotifyID,
                    name: name,
                    type: clientType,
                    serverId: serverId
                }
                RegWebNotifyMap[clientType][name] = callBack
            }

            var askItem = function () {
                var xhr = getHttpObj()
                xhr.onload = function (e) {
                    if (!WpsInvoke.RegWebNotifyEnable) {
                        RegWebNotifyMap[clientType][name] = null;
                        return;
                    }
                    if (xhr.responseText == "WPSInnerMessage_quit" || (xhr.status != undefined && xhr.status != 200) || !WpsInvoke.RegWebNotifyEnable) {
                        if ((!wpsclient || wpsclient.single)) {
                            window.setTimeout(askItem, 2000);
                        }
                        return;
                    }
                    window.setTimeout(askItem, 300)
                    // fix 新版本客户端使用旧版sdk的时候，会出现版本号判断问题
                    if (serverVersion !== "" && serverVersion !== "wait") {
                        try {
                            if (!HeartBeatWorker)
                                throw new Error();
                            var resText = JSON.parse(xhr.responseText);
                            paramStr.messageId = resText.msgId;
                            if (wpsclient) {
                                if (resText.data != undefined && paramStr.messageId != undefined)  // 如果发的数据是字符串化后的json对象，这里的resText.data就是一个json对象，可以输出自己想要的json数据
                                    if (typeof resText.data == 'object')
                                        wpsclient.OnRegWebNotify(JSON.stringify(resText.data))
                                    else
                                        wpsclient.OnRegWebNotify(resText.data)
                                else
                                    wpsclient.OnRegWebNotify(xhr.responseText)
                            } else {
                                var func = RegWebNotifyMap[clientType][name]
                                if (resText.data != undefined && paramStr.messageId != undefined)  // 如果发的数据是字符串化后的json对象，这里的resText.data就是一个json对象，可以输出自己想要的json数据
                                    if (typeof resText.data == 'object')
                                        func(JSON.stringify(resText.data))
                                    else
                                        func(resText.data)
                                else
                                    func(xhr.responseText)
                            }
                        } catch (e) {
                            // 这里做一个容错，即使json解析失败，也要把msgId提取出来，发回给服务端，避免消息清不掉一直重复发送
                            // 同时把data也取出来，但是格式无法保证
                            var data
                            var str = xhr.responseText
                            var idx1 = str.indexOf("\"msgId\"")
                            var idx2
                            var idx3
                            var idx4
                            var data
                            if (idx1 != -1) {
                                idx1 = idx1 + 8
                                idx2 = str.indexOf("\"data\"") - 2
                                paramStr.messageId = parseInt(str.substring(idx1, idx2))
                                idx3 = str.indexOf("\"data\"") + 8
                                idx4 = str.length - 2
                                data = str.substring(idx3, idx4)
                            }
                            if (wpsclient) {
                                if (paramStr.messageId !== undefined && data != undefined)
                                    wpsclient.OnRegWebNotify(data)
                                else
                                    wpsclient.OnRegWebNotify(xhr.responseText)
                            } else {
                                var func = RegWebNotifyMap[clientType][name]
                                if (paramStr.messageId !== undefined && data != undefined)
                                    func(data)
                                else
                                    func(xhr.responseText)
                            }
                        }
                    } else {
                        var resText = JSON.parse(xhr.responseText);
                        paramStr.messageId = resText.msgId;
                        var resData = decode(resText.data);
                        if (wpsclient) {
                            wpsclient.OnRegWebNotify(resData)
                        } else {
                            var func = RegWebNotifyMap[clientType][name]
                            func(resData)
                        }
                    }
                }
                xhr.onerror = function (e) {
                    if (bWebNotifyUseTimeout)
                        window.setTimeout(askItem, 1000)
                    else
                        window.setTimeout(askItem, 10000)
                }
                xhr.ontimeout = function (e) {
                    if (bWebNotifyUseTimeout)
                        window.setTimeout(askItem, 300)
                    else
                        window.setTimeout(askItem, 10000)
                }
                if (IEVersion() < 10) {
                    xhr.onreadystatechange = function () {
                        if (xhr.readyState != 4)
                            return;
                        if (xhr.bTimeout) {
                            return;
                        }
                        if (xhr.status === 200)
                            xhr.onload();
                        else
                            xhr.onerror();
                    }
                }
                xhr.open('POST', GetUrlBase() + '/askwebnotify', true)
                if (bWebNotifyUseTimeout)
                    xhr.timeout = 2000;
                if (HeartBeatWorker) {
                    g_LastRegTime = new Date().valueOf();
                    var param = {
                        param: {
                            name: name,
                            type: clientType,
                            businessId: g_businessId,
                            serverId: serverId
                        },
                        LastRegTime: g_LastRegTime
                    }
                    HeartBeatWorker.postMessage(param)
                }
                if (HeartBeatWorker) {
                    paramStr.businessId = g_businessId
                }
                xhr.send(JSON.stringify(paramStr));
            }

            // 用途ws发送消失后，客户端一段时间没有回复，自动关闭链接操作
            var askItemWebsocketTimer = null;
            // 客户端有消息推送标记
            var hasResponse = false;
            // 客户端无消息推送标记
            var isTimeout = false;
            var askItemWebsocket = function () {
                if (!isReplaceWebSocket()) {
                    askItem();
                    return
                }
                var ws = new getWebsocketObj();
                ws.connect(GetUrlBase() + '/askwebnotify');
                ws.onmessage = function (res) {
                    hasResponse = true;
                    ws.close();
                    if (askItemWebsocketTimer !== null) {
                        clearTimeout(askItemWebsocketTimer);
                        askItemWebsocketTimer = null;
                    }
                    if (!WpsInvoke.RegWebNotifyEnable) {
                        RegWebNotifyMap[clientType][name] = null;
                        return;
                    }
                    if (res.data == "WPSInnerMessage_quit" || httpCodeErrorMessageList.indexOf(res.data) != -1 || !WpsInvoke.RegWebNotifyEnable) {
                        if ((!wpsclient || wpsclient.single)) {
                            window.setTimeout(function () {
                                askItemWebsocket();
                            }, 2000);
                        }
                        return;
                    }
                    ;
                    window.setTimeout(function () {
                        askItemWebsocket();
                    }, 300);
                    // fix 新版本客户端使用旧版sdk的时候，会出现版本号判断问题
                    if (serverVersion !== "" && serverVersion !== "wait") {
                        try {
                            if (!HeartBeatWorker)
                                throw new Error();
                            var resText = JSON.parse(res.data);
                            paramStr.messageId = resText.msgId;
                            if (wpsclient) {
                                if (resText.data != undefined && paramStr.messageId != undefined)  // 如果发的数据是字符串化后的json对象，这里的resText.data就是一个json对象，可以输出自己想要的json数据
                                    if (typeof resText.data == 'object')
                                        wpsclient.OnRegWebNotify(JSON.stringify(resText.data))
                                    else
                                        wpsclient.OnRegWebNotify(resText.data)
                                else
                                    wpsclient.OnRegWebNotify(res.data)
                            } else {
                                var func = RegWebNotifyMap[clientType][name]
                                if (resText.data != undefined && paramStr.messageId != undefined)  // 如果发的数据是字符串化后的json对象，这里的resText.data就是一个json对象，可以输出自己想要的json数据
                                    if (typeof resText.data == 'object')
                                        func(JSON.stringify(resText.data))
                                    else
                                        func(resText.data)
                                else
                                    func(res.data)
                            }
                        } catch (e) {
                            // 这里做一个容错，即使json解析失败，也要把msgId提取出来，发回给服务端，避免消息清不掉一直重复发送
                            // 同时把data也取出来，但是格式无法保证
                            var data
                            var str = res.data
                            var idx1 = str.indexOf("\"msgId\"")
                            var idx2
                            var idx3
                            var idx4
                            var data
                            if (idx1 != -1) {
                                idx1 = idx1 + 8
                                idx2 = str.indexOf("\"data\"") - 2
                                paramStr.messageId = parseInt(str.substring(idx1, idx2))
                                idx3 = str.indexOf("\"data\"") + 8
                                idx4 = str.length - 2
                                data = str.substring(idx3, idx4)
                            }
                            if (wpsclient) {
                                if (paramStr.messageId !== undefined && data != undefined)
                                    wpsclient.OnRegWebNotify(data)
                                else
                                    wpsclient.OnRegWebNotify(res.data)
                            } else {
                                var func = RegWebNotifyMap[clientType][name]
                                if (paramStr.messageId !== undefined && data != undefined)
                                    func(data)
                                else
                                    func(res.data)
                            }
                        }
                    } else {
                        var resText = res.data;
                        paramStr.messageId = resText.msgId;
                        var resData = decode(resText.data);
                        if (wpsclient) {
                            wpsclient.OnRegWebNotify(resData)
                        } else {
                            var func = RegWebNotifyMap[clientType][name]
                            func(resData)
                        }
                    }
                };

                ws.onclose = function (e) {
                    // 正常有请求响应，不做处理由onmessage进行递归
                    if (hasResponse === true) {
                        hasResponse = false;
                        return
                    }
                    ;
                    // 超时处理
                    if (isTimeout === true) {
                        isTimeout = false;
                        if (bWebNotifyUseTimeout)
                            window.setTimeout(askItemWebsocket, 300);
                        else
                            window.setTimeout(askItemWebsocket, 10000);
                        return
                    }
                    ;
                    // 异常处理
                    if (bWebNotifyUseTimeout)
                        window.setTimeout(askItemWebsocket, 1000);
                    else
                        window.setTimeout(askItemWebsocket, 10000);
                }

                ws.onopen = function () {
                    if (HeartBeatWorker) {
                        paramStr.businessId = g_businessId
                    }
                    ws.send(JSON.stringify(paramStr));
                    askItemWebsocketTimer = window.setTimeout(function () {
                        isTimeout = true;
                        ws.close();
                        askItemWebsocketTimer = null;
                    }, 2000);
                };

                if (HeartBeatWorker) {
                    g_LastRegTime = new Date().valueOf();
                    var param = {
                        param: {
                            name: name,
                            type: clientType,
                            businessId: g_businessId,
                            serverId: serverId
                        },
                        LastRegTime: g_LastRegTime
                    }
                    HeartBeatWorker.postMessage(param)
                }
            };
            if (isReplaceWebSocket()) {
                askItemWebsocket();
            } else {
                askItem();
            }
        }

        /**
         * 获取服务端版本号的接口，这里主要是初始化一些参数
         * @param {*} clientType    加载项类型， wps / wpp / et
         * @param {*} name          加载项名称
         * @param {*} func          客户端加载项要执行的方法
         * @param {*} param         客户端加载项执行方法的参数
         * @param {*} callBack      回调函数
         * @param {*} showToFront   设置客户端是否显示到前面
         * @param {*} jsPluginsXml  设置加载项路径
         * @param {*} silentMode    是否是静默启动
         * @param {*} timeout       设置客户端启动超时时间（透传客户端内部执行），单位为ms
         * @param {*} startparam    传递给wps客户端的启动参数
         */
        function WpsStartWrapVersion(clientType, name, func, param, callBack, showToFront, jsPluginsXml, silentMode, timeout, startparam) {
            var paramEx = {
                jsPluginsXml: jsPluginsXml ? jsPluginsXml : "",
                showToFront: typeof (showToFront) == 'boolean' ? showToFront : true,
                param: (typeof (param) == 'object' ? param : JSON.parse(param))
            }
            var options = {
                clientType: clientType,
                name: name,
                func: func,
                param: paramEx,
                urlBase: GetUrlBase(),
                callBack: callBack,
                wpsclient: undefined,
                concurrent: true,
                silentMode: silentMode,
                timeout: timeout,
                startparam: startparam,
            }
            WpsStartWrapVersionInner(options);
        }

        //从外部浏览器远程调用 WPS 加载项中的方法
        var WpsInvoke = {
            InvokeAsHttp: WpsStartWrapVersion,
            InvokeAsHttps: WpsStartWrapVersion,
            RegWebNotify: RegWebNotify,
            ClientType: {
                wps: "wps",
                et: "et",
                wpp: "wpp",
                pdf: "pdf"
            },
            CreateXHR: getHttpObj,
            IsClientRunning: IsClientRunning,
            GetVersion: GetVersion,
            RegWebNotifyEnable: true
        }

        /**
         * @constructor WpsClient           wps客户端
         * @param {string} clientType       必传参数，加载项类型，有效值为"wps","wpp","et"；分别表示文字，演示，电子表格
         */
        function WpsClient(clientType) {
            /**
             * 设置RegWebNotify的回调函数，加载项给业务端发消息通过该函数
             * @memberof WpsClient
             * @member onMessage
             */
            this.onMessage;

            /**
             * 设置加载项路径
             * @memberof WpsClient
             * @member jsPluginsXml
             */
            this.jsPluginsXml;

            /**
             * 内部成员，外部无需调用
             */
            this.notifyRegsitered = false;
            this.clientId = "";
            this.concurrent = true;
            this.clientType = clientType;

            /**
             * 内部函数，外部无需调用
             * @param {*} options
             */
            this.initWpsClient = function (options) {
                options.clientType = this.clientType
                options.wpsclient = this
                options.concurrent = this.concurrent
                WpsStartWrapVersionInner(options)
            }

            /**
             * 以http启动
             * @param {string} name              加载项名称
             * @param {string} func              要调用的加载项中的函数行
             * @param {string} param             在加载项中执行函数func要传递的数据
             * @param {function({int, string})} callBack        回调函数，status = 0 表示成功，失败请查看message信息
             * @param {bool} showToFront         设置wps是否显示到前面来
             * @param {*} timeout       设置客户端启动超时时间（透传客户端内部执行），单位为ms
             * @param {*} startparam    传递给wps客户端的启动参数
             * @return {string}                  "Failed to send message to WPS." 发送消息失败，客户端已关闭；
             *                                   "WPS Addon is not response." 加载项阻塞，函数执行失败
             */
            this.InvokeAsHttp = function (name, func, param, callBack, showToFront, timeout, startparam) {
                function clientCallback(res) {
                    //this不是WpsClient，是options对象
                    if (res.status !== 0 || serverVersion < "1.0.1" || this.wpsclient.single == true) {
                        if (callBack)
                            callBack(res);
                        if (serverVersion < "1.0.1" || this.wpsclient.single == true)
                            RegWebNotify(clientType, name, this.wpsclient.onMessage)
                        return;
                    }
                    // fix 新版本客户端使用旧版sdk的时候，会出现版本号判断问题
                    if (serverVersion !== "" && serverVersion !== "wait") {
                        try {
                            var resObject = JSON.parse(res.response);
                            if (this.wpsclient.clientId == "") {
                                this.wpsclient.clientId = resObject.clientId;
                            }
                            if (typeof resObject.data == "object")
                                res.response = JSON.stringify(resObject.data);
                            else
                                res.response = resObject.data;
                        } catch (e) {
                            var str = res.response
                            var idx1 = str.indexOf("\"clientId\":\"{")
                            var idx2
                            var idx3
                            var idx4
                            if (idx1 != -1) {
                                idx1 = idx1 + ("\"clientId\":\"{").length - 1
                                idx2 = str.indexOf("\"data\":") - 3
                                if (this.wpsclient.clientId == "") {
                                    this.wpsclient.clientId = str.substring(idx1, idx2);
                                }
                                idx3 = str.indexOf("\"data\":") + ("\"data\":").length
                                idx4 = str.length - 1
                                if (idx3 < idx4)
                                    res.response = str.substring(idx3, idx4)
                                else
                                    res.response = "";
                            }
                        }
                    } else {
                        var resObject = JSON.parse(res.response);
                        if (this.wpsclient.clientId == "") {
                            this.wpsclient.clientId = resObject.clientId;
                        }
                        res.response = decode(resObject.data);
                    }
                    if (IEVersion() < 10)
                        eval(" res.response = '" + res.response + "';");
                    if (callBack)
                        callBack(res);
                    this.wpsclient.RegWebNotify(name);
                }

                var paramEx = {
                    jsPluginsXml: this.jsPluginsXml ? this.jsPluginsXml : "",
                    showToFront: typeof (showToFront) == 'boolean' ? showToFront : true,
                    param: (typeof (param) == 'object' ? param : JSON.parse(param))
                }
                this.initWpsClient({
                    name: name,
                    func: func,
                    param: paramEx,
                    urlBase: GetUrlBase(),
                    callBack: clientCallback,
                    timeout: timeout,
                    startparam: startparam,
                })
            }

            /**
             * 以https启动
             * @param {string} name              加载项名称
             * @param {string} func              要调用的加载项中的函数行
             * @param {string} param             在加载项中执行函数func要传递的数据
             * @param {function({int, string})} callBack        回调函数，status = 0 表示成功，失败请查看message信息
             * @param {*} timeout       设置客户端启动超时时间（透传客户端内部执行），单位为ms
             * @param {*} startparam    传递给wps客户端的启动参数
             * @param {bool} showToFront         设置wps是否显示到前面来
             */
            this.InvokeAsHttps = function (name, func, param, callBack, showToFront, timeout, startparam) {
                var paramEx = {
                    jsPluginsXml: this.jsPluginsXml ? this.jsPluginsXml : "",
                    showToFront: typeof (showToFront) == 'boolean' ? showToFront : true,
                    param: (typeof (param) == 'object' ? param : JSON.parse(param))
                }
                this.initWpsClient({
                    name: name,
                    func: func,
                    param: paramEx,
                    urlBase: GetUrlBase(),
                    callBack: callBack,
                    timeout: timeout,
                    startparam: startparam,
                })
            }

            /**
             * 内部函数，外部无需调用
             * @param {*} name
             */
            this.RegWebNotify = function (name) {
                RegWebNotify(this.clientType, name, null, this);
            }

            /**
             * 消息注册函数的回调函数
             * @param {*} message   客户端发来的消息
             */
            this.OnRegWebNotify = function (message) {
                if (this.onMessage)
                    this.onMessage(message)
            }

            /**
             * 以静默模式启动客户端
             * @param {string} name                 必传参数，加载项名称
             * @param {*} timeout       设置客户端启动超时时间（透传客户端内部执行），单位为ms
             * @param {*} startparam    传递给wps客户端的启动参数
             * @param {function({int, string})} [callBack]         回调函数，status = 0 表示成功，失败请查看message信息
             */
            this.StartWpsInSilentMode = function (name, callBack, timeout, startparam) {
                function initCallback(res) {
                    //this不是WpsClient，是options对象
                    if (res.status !== 0 || serverVersion < "1.0.1" || this.wpsclient.single == true) {
                        if (callBack)
                            callBack(res);
                        if (serverVersion < "1.0.1" || this.wpsclient.single == true)
                            RegWebNotify(clientType, name, this.wpsclient.onMessage)
                        return;
                    }
                    var jsonObj = JSON.parse(res.response);
                    if (this.wpsclient.clientId == "") {
                        this.wpsclient.clientId = jsonObj.clientId;
                    }
                    // fix 新版本客户端使用旧版sdk的时候，会出现版本号判断问题
                    if (serverVersion !== "" && serverVersion !== "wait") {
                        res.response = JSON.stringify(jsonObj.data);
                    } else {
                        res.response = decode(jsonObj.data);
                    }
                    if (callBack) {
                        callBack(res);
                    }
                    this.wpsclient.RegWebNotify(name);
                }

                var paramEx = {
                    jsPluginsXml: this.jsPluginsXml,
                    showToFront: false,
                    param: {status: "InitInSilentMode"}
                }
                this.silentMode = true;
                this.initWpsClient({
                    name: name,
                    func: "",
                    param: paramEx,
                    urlBase: GetUrlBase(),
                    callBack: initCallback,
                    timeout: timeout,
                    startparam: startparam,
                })
            }

            /**
             * 显示客户端到最前面
             * @param {string} name             必传参数，加载项名称
             * @param {function({int, string})} [callBack]     回调函数
             */
            this.ShowToFront = function (name, callBack) {
                if (serverVersion < "1.0.1") {
                    if (callBack) {
                        callBack({
                            status: 4,
                            message: "当前客户端不支持，请升级客户端"
                        });
                        return;
                    }
                    return;
                }
                if (this.clientId == "") {
                    if (callBack) callBack({
                        status: 3,
                        message: "没有静默启动客户端"
                    });
                    return;
                }
                var paramEx = {
                    jsPluginsXml: "",
                    showToFront: true,
                    param: {status: "ShowToFront"}
                }
                this.initWpsClient({
                    name: name,
                    func: "",
                    param: paramEx,
                    urlBase: GetUrlBase(),
                    callBack: callBack
                })
            }

            /**
             * 关闭未显示出来的静默启动客户端
             * @param {string} name             必传参数，加载项名称
             * @param {function({int, string})} [callBack]     回调函数
             */
            this.CloseSilentClient = function (name, callBack) {
                if (serverVersion < "1.0.1") {
                    if (callBack) {
                        callBack({
                            status: 4,
                            message: "当前客户端不支持，请升级客户端"
                        });
                        return;
                    }
                    return;
                }
                if (this.clientId == "") {
                    if (callBack) callBack({
                        status: 3,
                        message: "没有静默启动客户端"
                    });
                    return;
                }
                var paramEx = {
                    jsPluginsXml: "",
                    showToFront: false,
                    param: undefined
                }
                var func;
                if (this.clientType == "wps")
                    func = "wps.WpsApplication().Quit"
                else if (this.clientType == "et")
                    func = "wps.EtApplication().Quit"
                else if (this.clientType == "wpp")
                    func = "wps.WppApplication().Quit"

                function closeSilentClient(res) {
                    if (res.status == 0)
                        this.wpsclient.clientId = ""
                    if (callBack) callBack(res);
                    return;
                }

                this.initWpsClient({
                    name: name,
                    func: func,
                    param: paramEx,
                    urlBase: GetUrlBase(),
                    callBack: closeSilentClient
                })
            }

            /**
             * 当前客户端是否在运行，使用WpsClient.IsClientRunning()进行调用
             * @param {function({int, string})} [callBack]      回调函数，"Client is running." 客户端正在运行
             *                                                  "Client is not running." 客户端没有运行
             */
            this.IsClientRunning = function (callBack) {
                if (serverVersion < "1.0.1") {
                    if (callBack) {
                        callBack({
                            status: 4,
                            message: "当前客户端不支持，请升级客户端"
                        });
                        return;
                    }
                    return;
                }
                IsClientRunning(this.clientType, callBack, this)
            }
        }

        /**
         * 初始化sdk，用来减少在服务进程启动时自定义协议弹框出现的次数
         */
        var g_isSdkInited = false;

        function InitSdk(bMultiUser) {
            g_isSdkInited = false;
            var url = GetUrlBase() + "/version";
            startWps({
                url: url,
                sendData: JSON.stringify({serverId: serverId}),
                callBack: function (res) {
                    if ((!serverId && !bMultiUser) || bMultiUser)
                        g_isSdkInited = true;
                    if (res.status !== 0 && res.message !== "Subserver not available.") {
                        serverVersion = "wait"
                        return;
                    }
                    if (res.response && serverVersion == "wait") {
                        serverVersion = res.response;
                        sessionStorage.setItem('serverVersion', serverVersion);
                    }

                },
                tryCount: 1,
                bPop: false,
                concurrent: true,
                timeout: 1000
            });
        }

        InitSdk(false);

        function GetVersion(callBack) {
            var url = GetUrlBase() + "/version";
            startWps({
                url: url,
                data: "",
                callBack: function (res) {
                    callBack && callBack(res);
                    if (res.status !== 0 && res.message !== "Subserver not available.") {
                        serverVersion = "wait"
                        return;
                    }
                    if (res.response && serverVersion == "wait") {
                        serverVersion = res.response;
                        sessionStorage.setItem('serverVersion', serverVersion);
                    }
                },
                tryCount: 4,
                bPop: true,
                timeout: 5000
            });
        }

        if (typeof noGlobal === "undefined") {
            window.WpsInvoke = WpsInvoke;
            window.WpsClient = WpsClient;
            window.WebNotifyUseTimeout = WebNotifyUseTimeout;
            window.EnableMultiUser = EnableMultiUser;
        }

        /**
         * 当前客户端是否在运行，使用WpsInvoke.IsClientRunning()进行调用
         * @param {string} clientType       加载项类型
         * @param {function} [callBack]      回调函数，"Client is running." 客户端正在运行
         *                                   "Client is not running." 客户端没有运行
         */
        function IsClientRunning(clientType, callBack, wpsclient) {
            var url = GetUrlBase() + "/isRunning";
            var wrapper = {
                id: wpsclient == undefined ? undefined : wpsclient.clientId,
                app: clientType,
                serverId: serverId
            }
            wrapper = JSON.stringify(wrapper);
            startWps({
                url: url,
                sendData: wrapper,
                callBack: callBack,
                tryCount: 1,
                bPop: false,
                timeout: 2000,
                concurrent: true,
                wpsclient: wpsclient
            });
        }

        /**
         * 获取publish.xml的内容
         * @param {*} callBack 回调函数
         */
        function WpsAddonGetAllConfigInner(callBack) {
            var options = {
                url: GetUrlBase() + "/publishlist",
                type: "POST",
                sendData: JSON.stringify({serverId: serverId}),
                callBack: callBack,
                tryCount: 3,
                bPop: true,
                timeout: 5000,
                concurrent: true,
            }
            startWps(options);
        }

        /**
         * 先获取一下版本号
         * @param {*} callBack 回调函数
         */
        function WpsAddonGetAllConfig(callBack) {
            var options = {
                callBack: callBack,
                isPublish: true,
                urlBase: GetUrlBase()
            }
            WpsStartWrapVersionInner(options);
        }

        /**
         * 检查ribbon.xml文件是否有效
         * @param {*} element   参数对象
         * @param {*} callBack  回调函数
         */
        function WpsAddonVerifyStatus(element, callBack) {
            var xmlReq = getHttpObj();
            var offline = element.online === "false";
            var url = offline ? element.url : element.url + "ribbon.xml";
            xmlReq.open("POST", GetUrlBase() + "/redirect/runParams");
            xmlReq.onload = function (res) {
                if (offline && !res.target.response.startsWith("7z")) {
                    callBack({status: 1, msg: "不是有效的7z格式" + url});
                } else if (!offline && !res.target.response.startsWith("<customUI")) {
                    callBack({status: 1, msg: "不是有效的ribbon.xml, " + url})
                } else {
                    callBack({status: 0, msg: "OK"})
                }
            }
            xmlReq.onerror = function (res) {
                xmlReq.bTimeout = true;
                callBack({status: 2, msg: "网页路径不可访问，如果是跨域问题，不影响使用" + url})
            }
            xmlReq.ontimeout = function (res) {
                xmlReq.bTimeout = true;
                callBack({status: 3, msg: "访问超时" + url})
            }
            if (IEVersion() < 10) {
                xmlReq.onreadystatechange = function () {
                    if (xmlReq.readyState != 4)
                        return;
                    if (xmlReq.bTimeout) {
                        return;
                    }
                    if (xmlReq.status === 200)
                        xmlReq.onload();
                    else
                        xmlReq.onerror();
                }
            }
            xmlReq.timeout = 5000;
            var data = {
                method: "get",
                url: url,
                data: ""
            }
            var sendData = FormatSendData(data)
            xmlReq.send(sendData);
        }

        function WpsAddonVerifyStatusWebSocket(element, callBack) {
            var timer = null
            var ws = getWebsocketObj();
            var offline = element.online === "false";
            var url = offline ? element.url : element.url + "ribbon.xml";
            ws.connect(GetUrlBase() + "/redirect/runParams");
            ws.onmessage = function (res) {
                if (timer !== null) {
                    clearTimeout(timer);
                    timer = null;
                }
                ;
                if (offline && !res.data.startsWith("7z")) {
                    callBack({status: 1, msg: "不是有效的7z格式" + url});
                } else if (!offline && !res.data.startsWith("<customUI")) {
                    callBack({status: 1, msg: "不是有效的ribbon.xml, " + url})
                } else {
                    callBack({status: 0, msg: "OK"})
                }
                ws.close();
            }
            ws.onerror = function () {
                ws.bTimeout = true;
                callBack({status: 2, msg: "网页路径不可访问，如果是跨域问题，不影响使用" + url})
            }
            var data = {
                method: "get",
                url: url,
                data: ""
            }
            var sendData = FormatSendData(data)
            ws.onopen = function () {
                ws.send(sendData);
            }
            timer = setTimeout(function () {
                callBack({status: 3, msg: "访问超时" + url});
                timer = null;
                ws.close();
            }, 5000)
        }

        /**
         * 部署加载项，包括启动enable / disable禁用 / disableall禁用所有
         * @param {*} element   参数对象
         * @param {*} cmd       具体操作，enable / disable / disableall
         * @param {*} callBack  回调函数
         */
        function WpsAddonHandleEx(element, cmd, callBack) {
            var data = FormatData(element, cmd);
            startWps({
                url: GetUrlBase() + "/deployaddons/runParams",
                type: "POST",
                sendData: data,
                callBack: callBack,
                tryCount: 3,
                bPop: true,
                timeout: 0,
                concurrent: true
            });
        }

        /**
         * 启用加载项
         * @param {*} element   参数对象
         * @param {*} callBack  回调函数
         */
        function WpsAddonEnable(element, callBack) {
            WpsAddonHandleEx(element, "enable", callBack)
        }

        /**
         * 禁用加载项
         * @param {*} element   参数对象
         * @param {*} callBack  回调函数
         */
        function WpsAddonDisable(element, callBack) {
            WpsAddonHandleEx(element, "disable", callBack)
        }

        /**
         * 禁用所有加载项
         * @param {*} element
         * @param {*} callBack
         */
        function WpsAddonDisableAll(element, callBack) {
            WpsAddonHandleEx(element, "disableall", callBack)
        }

        /**
         * 生成json格式的数据
         * @param {*} element   参数对象
         * @param {*} cmd       具体操作，enable / disable / disableall
         * @returns base64编码后的数据
         */
        function FormatData(element, cmd) {
            var data = {
                "cmd": cmd, //"enable", 启用， "disable", 禁用, "disableall", 禁用所有
                "name": element.name,
                "url": element.url,
                "addonType": element.addonType,
                "online": element.online,
                "version": element.version,
                "time": new Date().getTime()
            }
            return FormatSendData(data);
        }

        /**
         * 将json格式的数据字符串化，并进行base64编码
         * @param {*} data  数据
         * @returns base64编码后的数据
         */
        function FormatSendData(data) {
            var strData = JSON.stringify(data);
            if (IEVersion() < 10)
                eval("strData = '" + JSON.stringify(strData) + "';");

            if (serverVersion >= "1.0.2" && serverId != undefined) {
                var base64Data = encode(strData);
                return JSON.stringify({
                    serverId: serverId,
                    data: base64Data
                })
            } else {
                return encode(strData);
            }
        }

        var verifyStatus = function () {
        };
        if (isReplaceWebSocket()) {
            verifyStatus = WpsAddonVerifyStatusWebSocket
        } else {
            verifyStatus = WpsAddonVerifyStatus
        }

        //管理 WPS 加载项
        var WpsAddonMgr = {
            getAllConfig: WpsAddonGetAllConfig,
            verifyStatus: verifyStatus,
            enable: WpsAddonEnable,
            disable: WpsAddonDisable,
            disableall: WpsAddonDisableAll
        }

        if (typeof noGlobal === "undefined") {
            window.WpsAddonMgr = WpsAddonMgr;
        }
        return {
            WpsInvoke: WpsInvoke,
            WpsClient: WpsClient,
            WebNotifyUseTimeout: WebNotifyUseTimeout,
            EnableMultiUser: EnableMultiUser,
            WpsAddonMgr: WpsAddonMgr,
            version: "1.0.33"
        };
    });

    /**
     * 改动时间：2022-11-28 19:06
     * 改动说明：1、调整心跳触发时机，确认云服务版本号后才触发
     *          2、轮询客户端消息赋予businessId调整至请求发送前。避免心跳没创建的异常情况
     * 改动影响：http&ws下，心跳触发是否正常
     */
}